﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Windows.Storage;

namespace Sharepoint.Client
{
    /// <summary>
    /// login and queries.
    /// Created using the info found at:
    /// http://www.wictorwilen.se/Post/How-to-do-active-authentication-to-Office-365-and-SharePoint-Online.aspx
    /// and this:
    /// http://allthatjs.com/2012/03/28/remote-authentication-in-sharepoint-online/
    /// </summary>
    public class SPLogin
    {
        const string _authUrl="https://login.microsoftonline.com/extSTS.srf";
        const string _login="/_forms/default.aspx?wa=wsignin1.0";

        //namespaces present in the SAML response
        const string _nsS = "http://www.w3.org/2003/05/soap-envelope";
        const string _nswst = "http://schemas.xmlsoap.org/ws/2005/02/trust";
        const string _nswsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
        const string _nswsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
        const string _nswsa = "http://www.w3.org/2005/08/addressing";
        const string _nssaml = "urn:oasis:names:tc:SAML:1.0:assertion";
        const string _nswsp = "http://schemas.xmlsoap.org/ws/2004/09/policy";
        const string _nspsf = "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault";

        Uri _uri;

        /// <summary>
        /// Creates the connection object pointing to a sharepoint online site.
        /// </summary>
        /// <param name="siteUri">the url of the sponline site</param>
        public SPLogin(string siteUri)
        {
            _uri = new Uri(siteUri);
        }

        /// <summary>
        /// Does the Signin into sp online.
        /// </summary>
        /// <param name="userName">user name in plain text</param>
        /// <param name="password">password in plain text</param>
        /// <returns>true if successful, false otherwise</returns>
        /// <exception cref="System.Exception">May throw exceptions with errors</exception>
        public async Task<SPConnection> SigninAsync(string userName, string password)
        {
            //TODO: manage exceptions
            string token = await getTokenAsync(userName, password);
            if (token != null)
            {
                var cookies=await submitTokenAsync(token);
                if (cookies.GetCookies(_uri)["FedAuth"] != null)
                {
                    return new SPConnection(_uri,cookies);
                }
            }
            return null;
        }

        string _saml;
        /// <summary>
        /// Retrieves the auth token from microsoftonline.com used
        /// to authenticate into the sp site.
        /// </summary>
        /// <param name="userName">user name in plain text</param>
        /// <param name="password">password in plain text</param>
        /// <returns>a string with the token.</returns>
        /// <exception cref="System.Exception">when the token was not correct.</exception>
        private async Task<string> getTokenAsync(string userName, string password)
        {
            if (_saml == null)
            {
                //gets the SAML.xml file, that contains the standard SAML for a login request
                var stream = await Windows.ApplicationModel.Package.Current.InstalledLocation.OpenStreamForReadAsync("Sharepoint.Client\\Data\\SAML.xml");
                using (var reader = new StreamReader(stream))
                {
                    _saml = await reader.ReadToEndAsync();
                }
            }

            //fills the SAML with the needed data
            string tokenRequestXml = string.Format(_saml, userName, password, _uri.Host);

            var request = HttpWebRequest.CreateHttp(_authUrl);
            request.Method = "POST";
            var reqstream = await request.GetRequestStreamAsync();
            using (StreamWriter w = new StreamWriter(reqstream))
            {
                //writes the SAML into the post
                await w.WriteAsync(tokenRequestXml);
            }
            var response = await request.GetResponseAsync();

            var xDoc = XDocument.Load(response.GetResponseStream());

            //find the BinarySecurityToken and returns it
            var body=xDoc.Descendants(XName.Get("Body", _nsS)).FirstOrDefault();
            if (body != null)
            {
                var fault = body.Descendants(XName.Get("Fault", _nsS)).FirstOrDefault();
                if (fault != null)
                {
                    var error=fault.Descendants(XName.Get("text", _nspsf)).FirstOrDefault();
                    if (error != null)
                        throw new Exception(error.Value);
                }
                else
                {
                    var token = body.Descendants(XName.Get("BinarySecurityToken", _nswsse)).FirstOrDefault();
                    if (token != null)
                        return token.Value;
                }
            }

            return null;
        }

        /// <summary>
        /// Submits the token to the sp online site to 
        /// retrieve the auth cookies used for communication
        /// with the sp site.
        /// </summary>
        /// <param name="token">auth token retrieved with the getToken method</param>
        /// <returns>a <see cref="CookieCollection"/> with the auth cookies.</returns>
        private async Task<CookieContainer> submitTokenAsync(string token)
        {
            UriBuilder bldr = new UriBuilder(_uri.Scheme, _uri.Host, _uri.Port);

            var request = HttpWebRequest.CreateHttp( bldr.Uri+_login);
            request.CookieContainer = new CookieContainer();
            request.Method = "POST";
            var requestStream = await request.GetRequestStreamAsync();
            using (StreamWriter w = new StreamWriter(requestStream))
            {
                await w.WriteAsync(token);
            }
            var response = await request.GetResponseAsync();
            bldr.Path = null;
            return request.CookieContainer;//.GetCookies(bldr.Uri);
        }        
    }
}
